#pragma once

#include "../../externals/PixelToaster.h"
#include "../math/primitives.h"
#include "../math/meshobject.h"
#include "essential.h"
#include <Windows.h>
#include <assert.h>

using namespace PixelToaster;

// ================================================================================================
// Basic implementation of a viewport
// ================================================================================================
class Viewport {

private:

    Display display;                     // -- our display
    std::vector<Pixel> pixelbuffer;      // -- our pixel buffer
    uint width;                          // -- width of our viewport
    uint height;                         // -- height of our viewport
    float depth;                         // -- the distance to the plane of projection
    float tmax;                          // -- max ray distance

public:

    // -- lifetime
    Viewport(char*, uint width, uint height, float fov, float max);

    // -- accessor methods
    Display& GetDisplay();
    const Display& GetDisplay() const;
    Pixel* GetPixelBuffer();
    const Pixel* GetPixelBuffer() const;
    float GetDistToProj();
    uint GetWidth();
    uint GetHeight();

    // -- Public methods
    void RefreshBuffers(Rect2D& rect);
    void RefreshPixelBuffer();
    void Render(MeshObject& mobject, Rect2D& rect);
    void Render4x4(MeshObject& mobject, Rect2D& rect);
    void Render(MeshObject& mobject);
    void Render4x4(MeshObject& mobject);
    Vector4 Viewport::GetProjectedVerts(const Vector4& vec);
    int SurfaceIntersection(Ray& ray, float& tmax, const TriangleAcc& triaccel);
    void SurfaceIntersection(RaySSE& ray, float tmax[], const TriangleAccDP& triaccel);
    void Present();
 
    // -- Accessor methods
    float GetDepth() const;
    float GetMax() const;
};

// ================================================================================================
// Constructor
// ================================================================================================
inline Viewport::Viewport(char* name, uint width_, uint height_,  float fov, float max_) 
                          : width(width_), height(height_), display(name, width_,  height_), 
                            tmax(max_) {
    pixelbuffer.resize(int(width * height));
    float radfov = fov * kPi/180.0f;   
    depth = cos(radfov/2.0f)/sin(radfov/2.0f);
    RefreshPixelBuffer();
}

// ================================================================================================
// Accessor methods
// ================================================================================================
inline Display& Viewport::GetDisplay() {
    return display;
}

inline const Display& Viewport::GetDisplay() const {
    return display;
}

inline Pixel* Viewport::GetPixelBuffer() {
    return &pixelbuffer[0];
}

inline const Pixel* Viewport::GetPixelBuffer() const {
    return &pixelbuffer[0];
}

inline float Viewport::GetDistToProj() {
    return depth;
}

inline uint Viewport::GetWidth() {
    return width;
}

inline uint Viewport::GetHeight() {
    return height;
}

inline float Viewport::GetDepth() const {
    return depth;
}

inline float Viewport::GetMax() const {
    return tmax;
}

// ================================================================================================
// Refresh selected buffer selection
// ================================================================================================
inline void Viewport::RefreshBuffers(Rect2D& rect) {
    uint xstart = rect.topleft.x;
    uint ystart = rect.topleft.y;
    uint yend = rect.bottomright.y;

    uint indexstart = xstart + (ystart * width);
    uint indexend = (yend * width);

    for(uint i = indexstart; i < indexend; ++i) {
        pixelbuffer[i].r = 0.0f;
        pixelbuffer[i].g = 0.0f;
        pixelbuffer[i].b = 0.0f;
    }
}

// ================================================================================================
// Refresh the pixelbuffer
// ================================================================================================
inline void Viewport::RefreshPixelBuffer() {
    for(uint i = 0; i < pixelbuffer.size(); ++i) {
        pixelbuffer[i].r = 0.0f;
        pixelbuffer[i].g = 0.0f;
        pixelbuffer[i].b = 0.0f;
    }
}

// ================================================================================================
// Write to the whole buffer
// ================================================================================================
inline void Viewport::Render(MeshObject& mobject) {
    Point2D topleft = {0,0};
    Point2D bottomright = {width, height};
    Rect2D rect = {topleft, bottomright};
    Render(mobject, rect);
}

// ================================================================================================
// Presents the buffers to the display
// ================================================================================================
inline void Viewport::Present() {
    display.update(pixelbuffer);
}
